﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using HtmlAgilityPack;
using System.Net;
using System.ComponentModel.Composition;
using NthDownload.Composition;
using System.Threading.Tasks;
using System.Threading;

namespace NthDownload.DownloadPlugins.Http
{
    public class HttpNode : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion INotifyPropertyChanged

        #region Class Members

        private string _path;
        private bool _isExpanded;
        private bool _isSelected;
        private bool _isLoadingChildren;
        private bool _cancelLoadingChildren;
        private ObservableCollection<HttpNode> _children;
        private object _loadingSyncContext = new object();

        #endregion Class Members

        #region Constructors

        public HttpNode() 
            : this("")
        {
        }

        public HttpNode(string path)
        {
            Path = path;
            IsExpanded = false;
            IsLoadingChildren = false;
            Children = new ObservableCollection<HttpNode>();

            SatisfyImports();
        }

        #endregion Constructors

        #region Methods

        /// <summary>
        /// Loads all the a tags the path and creates child nodes from them
        /// </summary>
        public Task LoadChildren(bool expandAfterLoad)
        {
            Task task;

            //
            // Check if children are already being loaded
            //
            lock (_loadingSyncContext)
            {
                if (IsLoadingChildren)
                {
                    task = new Task(() => { });
                    task.Start();
                    return task;
                }
                else
                {
                    IsLoadingChildren = true;
                }
            }

            task = new Task(() =>
            {
                //
                // Load html document
                //
                HttpCredentials httpCredentials = Settings.Credentials.Where(c => Path.ToLower().StartsWith(c.Domain.ToLower())).FirstOrDefault();
                HtmlWeb htmlWeb = new HtmlWeb();
                HtmlDocument htmlDocument;

                if (httpCredentials != null)
                {
                    WebProxy proxy = new WebProxy();
                    htmlDocument = htmlWeb.Load(Path, "GET", proxy, new NetworkCredential(httpCredentials.Username, httpCredentials.Password));
                }
                else
                {
                    htmlDocument = htmlWeb.Load(Path);
                }

                //
                // Get all hrefs of a tags
                //
                List<string> paths = htmlDocument.DocumentNode.Descendants("a").SelectMany(e => e.Attributes.Where(a => a.Name == "href").Select(a => a.Value)).Distinct().ToList();

                //
                // Build child nodes
                //
                UISynchronizationContext.Send(o =>
                {
                    Children.Clear();
                }, null);

                foreach (string path in paths)
                {
                    string fullPath = path;

                    //
                    // If a relative path, use the current path as a base
                    //
                    Uri uri;
                    if (Uri.TryCreate(path, UriKind.Relative, out uri))
                    {
                        Uri parentUri = new Uri(Path);
                        Uri fullUri = new Uri(parentUri, uri);
                        fullPath = fullUri.AbsoluteUri;
                    }

                    Uri testUri;
                    if (Uri.TryCreate(fullPath, UriKind.Absolute, out testUri) && !Path.Contains(fullPath))
                    {
                        UISynchronizationContext.Send(o =>
                        {
                            Children.Add(new HttpNode(fullPath));
                        }, null);
                    }
                }

                //
                // Check if set to expand
                //
                if (expandAfterLoad)
                {
                    UISynchronizationContext.Send(o =>
                    {
                        IsExpanded = true;
                    }, null);
                }

                //
                // Maintain is loading statud
                //
                lock (_loadingSyncContext)
                {
                    IsLoadingChildren = false;
                }
            }, TaskCreationOptions.LongRunning);

            task.Start();

            return task;
        }

        /// <summary>
        /// Loads all the a tags the path and creates child nodes from them
        /// </summary>
        //public Task LoadChildrenOld(int depth, int count, bool expandAfterLoad)
        //{
        //    Task task;

        //    //
        //    // Check if children are already being loaded
        //    //
        //    lock (_loadingSyncContext)
        //    {
        //        if (IsLoadingChildren)
        //        {
        //            task = new Task(() => { });
        //            task.Start();
        //            return task;
        //        }
        //        else
        //        {
        //            IsLoadingChildren = true;
        //        }
        //    }

        //    task = new Task(() =>
        //    {
        //        //
        //        // Load html document
        //        //
        //        HttpCredentials httpCredentials = Settings.Credentials.Where(c => Path.ToLower().StartsWith(c.Domain.ToLower())).FirstOrDefault();
        //        HtmlWeb htmlWeb = new HtmlWeb();
        //        HtmlDocument htmlDocument;

        //        if (httpCredentials != null)
        //        {
        //            WebProxy proxy = new WebProxy();
        //            htmlDocument = htmlWeb.Load(Path, "GET", proxy, new NetworkCredential(httpCredentials.Username, httpCredentials.Password));
        //        }
        //        else
        //        {
        //            htmlDocument = htmlWeb.Load(Path);
        //        }

        //        //
        //        // Get all hrefs of a tags
        //        //
        //        List<string> paths = htmlDocument.DocumentNode.Descendants("a").SelectMany(e => e.Attributes.Where(a => a.Name == "href").Select(a => a.Value)).Distinct().ToList();

        //        //
        //        // Build child nodes
        //        //
        //        UISynchronizationContext.Send(o =>
        //        {
        //            Children.Clear();
        //        }, null);

        //        foreach (string path in paths)
        //        {
        //            string fullPath = path;

        //            //
        //            // If a relative path, use the current path as a base
        //            //
        //            Uri uri;
        //            if (Uri.TryCreate(path, UriKind.Relative, out uri))
        //            {
        //                Uri parentUri = new Uri(Path);
        //                Uri fullUri = new Uri(parentUri, uri);
        //                fullPath = fullUri.AbsoluteUri;
        //            }

        //            Uri testUri;
        //            if (Uri.TryCreate(fullPath, UriKind.Absolute, out testUri) && !Path.Contains(fullPath))
        //            {
        //                UISynchronizationContext.Send(o =>
        //                {
        //                    Children.Add(new HttpNode(fullPath));
        //                }, null);
        //            }
        //        }

        //        //
        //        // Check if set to expand
        //        //
        //        if (expandAfterLoad)
        //        {
        //            UISynchronizationContext.Send(o =>
        //            {
        //                IsExpanded = true;
        //            }, null);
        //        }

        //        //
        //        // Recursively load children to depth threshold
        //        //
        //        depth--;
        //        if (depth > 0)
        //        {
        //            List<Task> recursiveTasks = new List<Task>();

        //            foreach (HttpNode httpNode in Children)
        //            {
        //                Task recursiveTask = httpNode.LoadChildren(depth, count, false);
        //                recursiveTasks.Add(recursiveTask);

        //                if (CancelLoadingChildren)
        //                {
        //                    CancelLoadingChildren = false;
        //                    break;
        //                }
        //                //
        //                // If a count is specified then wait when that count is reached until all recursive calls are finished 
        //                //
        //                if (count > 0 && recursiveTasks.Count >= count)
        //                {
        //                    Task.WaitAll(recursiveTasks.ToArray());
        //                    recursiveTasks.Clear();
        //                }
        //            }

        //            Task.WaitAll(recursiveTasks.ToArray());
        //        }

        //        //
        //        // Maintain is loading statud
        //        //
        //        lock (_loadingSyncContext)
        //        {
        //            IsLoadingChildren = false;
        //        }
        //    }, TaskCreationOptions.LongRunning);

        //    task.Start();

        //    return task;
        //}

        /// <summary>
        /// Signals that any active load children operation should stop
        /// </summary>
        public void CancelLoadChildren()
        {
            //
            // Check if children are already being loaded
            //
            lock (_loadingSyncContext)
            {
                if (IsLoadingChildren)
                {
                    CancelLoadingChildren = true;
                }
                else
                {
                    CancelLoadingChildren = false;
                    return;
                }
            }
        }

        /// <summary>
        /// Gets all selected nodes recursively
        /// </summary>
        public List<HttpNode> GetSelectedRecursively()
        {
            List<HttpNode> selectedNodes = new List<HttpNode>();

            if (IsSelected)
            {
                selectedNodes.Add(this);
            }

            foreach (HttpNode childNode in Children)
            {
                selectedNodes.AddRange(childNode.GetSelectedRecursively());
            }

            return selectedNodes;
        }

        #endregion Methods

        #region Protected Methods

        /// <summary>
        /// Satisfies imports for the current view model
        /// </summary>
        protected void SatisfyImports()
        {
            ImportService.TrySatisfyImports(this);
        }

        #endregion Protected Methods

        #region Private Methods

        /// <summary>
        /// Asynchronously loads the HttpNodes for a given path
        /// </summary>
        private Task<List<HttpNode>> GetNodesFromPath(string rootPath)
        {
            Task<List<HttpNode>> task = new Task<List<HttpNode>>(() =>
            {
                //
                // Load html document
                //
                HttpCredentials httpCredentials = Settings.Credentials.Where(c => rootPath.ToLower().StartsWith(c.Domain.ToLower())).FirstOrDefault();
                HtmlWeb htmlWeb = new HtmlWeb();
                HtmlDocument htmlDocument;

                if (httpCredentials != null)
                {
                    WebProxy proxy = new WebProxy();
                    htmlDocument = htmlWeb.Load(rootPath, "GET", proxy, new NetworkCredential(httpCredentials.Username, httpCredentials.Password));
                }
                else
                {
                    htmlDocument = htmlWeb.Load(rootPath);
                }

                //
                // Get all hrefs of a tags
                //
                List<string> paths = htmlDocument.DocumentNode.Descendants("a").SelectMany(e => e.Attributes.Where(a => a.Name == "href").Select(a => a.Value)).Distinct().ToList();

                //
                // Create http nodes from paths
                //
                List<HttpNode> httpNodes = new List<HttpNode>();
                foreach (string path in paths)
                {
                    string fullPath = path;

                    //
                    // If a relative path, use the current path as a base
                    //
                    Uri uri;
                    if (Uri.TryCreate(path, UriKind.Relative, out uri))
                    {
                        Uri parentUri = new Uri(Path);
                        Uri fullUri = new Uri(parentUri, uri);
                        fullPath = fullUri.AbsoluteUri;
                    }

                    Uri testUri;
                    if (Uri.TryCreate(fullPath, UriKind.Absolute, out testUri) && !Path.Contains(fullPath))
                    {
                        httpNodes.Add(new HttpNode(fullPath));
                    }
                }

                return httpNodes;
            });

            task.Start();
            return task;
        }

        #endregion Private Methods

        #region Properties

        /// <summary>
        /// Indicates if a loading children operation should be canceled
        /// </summary>
        public bool CancelLoadingChildren
        {
            get
            {
                return _cancelLoadingChildren;
            }
            private set
            {
                _cancelLoadingChildren = value;
                OnPropertyChanged("CancelLoadingChildren");
            }
        }

        /// <summary>
        /// The path this node points to
        /// </summary>
        public string Path
        {
            get
            {
                return _path;
            }
            set
            {
                _path = value;
                OnPropertyChanged("Path");
            }
        }

        /// <summary>
        /// Indicates if the node should be expanded
        /// </summary>
        public bool IsExpanded
        {
            get
            {
                return _isExpanded;
            }
            set
            {
                _isExpanded = value;
                OnPropertyChanged("IsExpanded");
            }
        }

        /// <summary>
        /// Indicates if the node is selected
        /// </summary>
        public bool IsSelected
        {
            get
            {
                return _isSelected;
            }
            set
            {
                _isSelected = value;
                OnPropertyChanged("IsSelected");
            }
        }

        /// <summary>
        /// Indicates if the node is loading it's children
        /// </summary>
        public bool IsLoadingChildren
        {
            get
            {
                return _isLoadingChildren;
            }
            set
            {
                _isLoadingChildren = value;
                OnPropertyChanged("IsLoadingChildren");
            }
        }

        /// <summary>
        /// This nodes children
        /// </summary>
        public ObservableCollection<HttpNode> Children
        {
            get
            {
                return _children;
            }
            set
            {
                _children = value;
                OnPropertyChanged("Children");
            }
        }

        /// <summary>
        /// Current Settings
        /// </summary>
        [Import(AllowRecomposition = true)]
        public HttpSettings Settings { get; set; }

        /// <summary>
        /// The Synchronization Context of the executing thread
        /// </summary>
        [Import(typeof(SynchronizationContext), AllowRecomposition = true)]
        public SynchronizationContext UISynchronizationContext { get; set; }

        #endregion Properties

        #region Private Classes

        /// <summary>
        /// Stores the information for the nodes being loaded at a certain level in a node tree
        /// </summary>
        private class NodeLoadingData
        {
            public NodeLoadingData(List<HttpNode> nodes, List<int> loadedIndexes, int currentIndex)
            {
                Nodes = nodes;
                LoadedIndexes = loadedIndexes;
                CurrentIndex = currentIndex;
            }

            public List<HttpNode> Nodes { get; set; }
            public List<int> LoadedIndexes { get; set; }
            public int CurrentIndex { get; set; }
        }

        #endregion Private Classes
    }
}
